/*
 * Copyright (C) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.googlecode.android_scripting;

import android.annotation.SuppressLint;
import com.googlecode.android_scripting.interpreter.InterpreterConstants;
import com.trilead.ssh2.StreamGobbler;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

@SuppressLint("SdCardPath")
public class Process {

	private static final int DEFAULT_BUFFER_SIZE = 8192;

	private final List<String> mArguments;
	private final Map<String, String> mEnvironment;

	private static final int PID_INIT_VALUE = -1;

	private File mBinary;
	private String mName;
	private long mStartTime;
	private long mEndTime;

	protected final AtomicInteger mPid;
	protected FileDescriptor mFd;
	protected OutputStream mOut;
	protected InputStream mIn;
	protected File mLog;

	public Process() {
		mArguments = new ArrayList<String>();
		mEnvironment = new HashMap<String, String>();
		mPid = new AtomicInteger(PID_INIT_VALUE);
	}

	public void addArgument(String argument) {
		mArguments.add(argument);
	}

	public void addAllArguments(List<String> arguments) {
		mArguments.addAll(arguments);
	}

	public void putAllEnvironmentVariables(Map<String, String> environment) {
		mEnvironment.putAll(environment);
	}

	public void putEnvironmentVariable(String key, String value) {
		mEnvironment.put(key, value);
	}

	public void setBinary(File binary) {
		if (!binary.exists()) {
			throw new RuntimeException("Binary " + binary + " does not exist!");
		}
		mBinary = binary;
	}

	public Integer getPid() {
		return mPid.get();
	}

	public FileDescriptor getFd() {
		return mFd;
	}

	public OutputStream getOut() {
		return mOut;
	}

	public OutputStream getErr() {
		return getOut();
	}

	public File getLogFile() {
		return mLog;
	}

	public InputStream getIn() {
		return mIn;
	}

	public void start(final Runnable shutdownHook) {
		if (isAlive()) {
			throw new RuntimeException(
					"Attempted to start process that is already running.");
		}

		String binaryPath = mBinary.getAbsolutePath();
		Log.v("Executing " + binaryPath + " with arguments " + mArguments
				+ " and with environment " + mEnvironment.toString());

		int[] pid = new int[1];
		String[] argumentsArray = mArguments.toArray(new String[mArguments
				.size()]);
		String path = null;
		if (FileUtils.externalStorageMounted()) {
			path = InterpreterConstants.SDCARD_SL4A_ROOT;
		} else {
			path = "/data/data/com.googlecode.android_scripting/files/sl4a/";
		}
		path = path + "logs/";
		File logs = new File(path);
		if (!logs.exists()) {
			logs.mkdirs();
		}
		mLog = new File(String.format("%s/%s.log", path, getName()));

		mFd = Exec.createSubprocess(binaryPath, argumentsArray,
				getEnvironmentArray(), getWorkingDirectory(), pid);
		mPid.set(pid[0]);
		mOut = new FileOutputStream(mFd);
		mIn = new StreamGobbler(new FileInputStream(mFd), mLog,
				DEFAULT_BUFFER_SIZE);
		mStartTime = System.currentTimeMillis();

		new Thread(new Runnable() {
			public void run() {
				int result = Exec.waitFor(mPid.get());
				mEndTime = System.currentTimeMillis();
				int pid = mPid.getAndSet(PID_INIT_VALUE);
				Log.v("Process " + pid + " exited with result code " + result
						+ ".");
				try {
					mIn.close();
				} catch (IOException e) {
					Log.e(e);
				}
				try {
					mOut.close();
				} catch (IOException e) {
					Log.e(e);
				}
				if (shutdownHook != null) {
					shutdownHook.run();
				}
			}
		}).start();
	}

	private String[] getEnvironmentArray() {
		List<String> environmentVariables = new ArrayList<String>();
		for (Entry<String, String> entry : mEnvironment.entrySet()) {
			environmentVariables.add(entry.getKey() + "=" + entry.getValue());
		}
		String[] environment = environmentVariables
				.toArray(new String[environmentVariables.size()]);
		return environment;
	}

	public void kill() {
		if (isAlive()) {
			android.os.Process.killProcess(mPid.get());
			Log.v("Killed process " + mPid);
		}
	}

	public boolean isAlive() {
		return (mFd != null && mFd.valid()) && mPid.get() != PID_INIT_VALUE;
	}

	public String getUptime() {
		long ms;
		if (!isAlive()) {
			ms = mEndTime - mStartTime;
		} else {
			ms = System.currentTimeMillis() - mStartTime;
		}
		StringBuilder buffer = new StringBuilder();
		int days = (int) (ms / (1000 * 60 * 60 * 24));
		int hours = (int) (ms % (1000 * 60 * 60 * 24)) / 3600000;
		int minutes = (int) (ms % 3600000) / 60000;
		int seconds = (int) (ms % 60000) / 1000;
		if (days != 0) {
			buffer.append(String.format("%02d:%02d:", days, hours));
		} else if (hours != 0) {
			buffer.append(String.format("%02d:", hours));
		}
		buffer.append(String.format("%02d:%02d", minutes, seconds));
		return buffer.toString();
	}

	public String getName() {
		return mName;
	}

	public void setName(String name) {
		mName = name;
	}

	public String getWorkingDirectory() {
		return null;
	}
}
